#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/cpufreq.h>

#include <asm/hardware.h>

spinlock_t sep4020_cpufreq_lock;

#define SEP4020_NR_FREQ 11
#define CPU_RECONFIG_WAIT_TIME 5000000/* in 10^(-9) s = nanoseconds */

typedef struct
{
	int speed;
	u32 sdconfig1;
	u32 sdconfig2;
}sep4020_sdram_regs_t;

static struct cpufreq_driver sep4020_driver;

static sep4020_sdram_regs_t sep4020_sdram_settings[] =
{
	/*speed,		SDCONFIG1	SDCONFIG2*/
	{	32000,	0x1E104177,	0x00001860},
	{	36000,	0x1E104177,	0x00001860},
	{	40000,	0x1E104177,	0x00001860},
	{	44000,	0x1E104177,	0x00001860},
	{	48000,	0x1E104177,	0x00001860},
	{	52000,	0x1E104177,	0x00001860},
	{	56000,	0x1E104177,	0x00001860},
	{	64000,	0x1E104177,	0x00001860},
	{	72000,	0x1E104177,	0x00001860},
	{	80000,	0x1E104177,	0x00001860},
	{	88000,	0x1E104177,	0x00001860},	
	{	0,			0,				0}/*LAST ENTRY*/
};

static int sep4020_freq_to_index(unsigned int khz)
{
	int i;
	for(i = 0; i < SEP4020_NR_FREQ; i++)
		if(sep4020_sdram_settings[i].speed >= khz)
		break;

	return i;
}

static unsigned int sep4020_index_to_freq(int index)
{
	unsigned int freq = 0;
	if(index < SEP4020_NR_FREQ)
		freq = sep4020_sdram_settings[index].speed;
	return freq;
}

static int sep4020_freq_reconfig(unsigned int cpu_freq_khz)
{
	unsigned int freq = 0;
	int pmcr_pre	= 0;
	int pmcr_after	= 0;

	printk("in the freq reconfig ,the freq is %d\n",cpu_freq_khz);

	freq = cpu_freq_khz/1000;
	if(freq >= 56)
	{
		pmcr_pre = 0x4000 + freq/8;
	}
	else
	{
		pmcr_pre = freq/4;
	}
	pmcr_after = pmcr_pre + 0x8000;
	
	*(unsigned int *)PMU_PMCR_V= pmcr_pre;
   *(unsigned int *)PMU_PMCR_V= pmcr_after;
	
}

static unsigned int sep4020_getspeed(unsigned int cpu)
{	
	int pmcr_pre;
	if(cpu)
		return 0;
	pmcr_pre = *(volatile unsigned long*)PMU_PMCR_V ;
	if(pmcr_pre > 0x4000)
		return (pmcr_pre-0x4000)*8000;
	else 
		return (pmcr_pre)*4000;
}

static unsigned int sep4020_systimer_reconfig(unsigned int cpu_freq_khz)
{
	unsigned int tmp_latch;

	tmp_latch = (cpu_freq_khz*1000+ HZ/2)/HZ;
	*(volatile unsigned long*)TIMER_T1LCR_V = tmp_latch;
	return 0;
}

static void sep4020_update_sdram_timings(int current_speed, int new_speed)
{
	sep4020_sdram_regs_t *settings = sep4020_sdram_settings;
	while(settings->speed != 0)
	{
		if(new_speed == settings->speed)
			break;
		settings++;
	}

	if (settings->speed == 0)
	{
		panic("%s: couldn't find dram setting for speed %d\n",
		      __FUNCTION__, new_speed);
	}

	
	/* No risk, no fun: run with interrupts on! */
	*(volatile unsigned long*)EMI_SDCONF1_V = settings->sdconfig1;
	*(volatile unsigned long*)EMI_SDCONF2_V = settings->sdconfig2;		
		

	
}

static int sep4020_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation)
{
	unsigned int cur = sep4020_getspeed(0);
	int speed_index = sep4020_freq_to_index(target_freq);
	struct cpufreq_freqs freqs;

	switch(relation)
	{
	case CPUFREQ_RELATION_L:
		if (sep4020_index_to_freq(speed_index) > policy->max)
			speed_index--;
		break;
	case CPUFREQ_RELATION_H:
		if ((sep4020_index_to_freq(speed_index) > target_freq) &&
		    (sep4020_index_to_freq(speed_index - 1) >= policy->min))
			speed_index--;
		break;
	}

	freqs.old = cur;
	freqs.new = sep4020_index_to_freq(speed_index);
	freqs.cpu = 0;

	//to protect the transition of changing freq
	spin_lock_irq(&sep4020_cpufreq_lock);

	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);

	//the sequence below is important, this is experience.
	//if (freqs.new > cur)
	//	sep4020_update_sdram_timings(cur, freqs.new);

	sep4020_freq_reconfig(freqs.new);
	sep4020_systimer_reconfig(freqs.new);

	//if (freqs.new < cur)
	/****sdram could not change when the code is still in sdram, we can put the sdram-timing code in esram, this function is not complete yet.**/
	//	sep4020_update_sdram_timings(cur, freqs.new);

	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);

	spin_unlock_irq(&sep4020_cpufreq_lock);

	return 0;

}

static unsigned int sep4020_verify_speed(struct cpufreq_policy *policy)
{
	printk("now in the verify\n");

	if (policy->cpu)
		return -EINVAL;

	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, policy->cpuinfo.max_freq);
	return 0;
}



static int __init sep4020_cpu_init(struct cpufreq_policy *policy)
{
	if(policy->cpu != 0) 
		return -EINVAL;
	
	policy->cur  = policy->max = sep4020_getspeed(0);
	policy->min	= 32000;
	policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
	policy->cpuinfo.min_freq = 32000;
	policy->cpuinfo.max_freq = 88000;
	policy->cpuinfo.transition_latency = CPU_RECONFIG_WAIT_TIME;
	return 0;
}

static struct cpufreq_driver sep4020_driver = 
{
	.flags	= CPUFREQ_STICKY,
	.verify	= sep4020_verify_speed, 
	.target	= sep4020_target,
	.get		= sep4020_getspeed,
	.init		= sep4020_cpu_init,
	.name		= "SEP4020 Freq",
};

static int __init sep4020_cpufreq_init(void)
{
	return cpufreq_register_driver(&sep4020_driver);
}

arch_initcall(sep4020_cpufreq_init);
